package gui.teamEditor;

import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JTabbedPane;

import org.xml.sax.SAXException;

import common.Dice;

import data.Player;
import data.Team;
import data.Data.Race;
import data.Data.Role;
import data.Data.Skill;
import exception.TeamException;

/**
 * The team editor opens, saves and modifies teams and players. By using PlayerPanels,
 * we leverage the serialization code already present in the Team and Player classes.
 * 
 * @author Julien
 *
 */
public class TeamEditor extends JFrame implements ActionListener {
	private static final long serialVersionUID = 2926108052989709839L;	
	
	// Top-level menus
	private JMenuBar m_menu = new JMenuBar();
	private JMenu m_file    = new JMenu("File");
	private JMenu m_actions = new JMenu("Team");
	
	// File sub-menus
	private JMenuItem m_new    = new JMenuItem("New");
	private JMenuItem m_open   = new JMenuItem("Open");
	private JMenuItem m_save   = new JMenuItem("Save");
	private JMenuItem m_saveAs = new JMenuItem("Save As...");
	private JMenuItem m_quit   = new JMenuItem("Quit");
	
	// Actions submenus
	private JMenuItem m_addPlayer       = new JMenuItem("Add new player...");
	private JMenuItem m_removePlayer    = new JMenuItem("Remove player");
	private JMenuItem m_levelUpPlayer   = new JMenuItem("Level up!");
	private JMenuItem m_levelDownPlayer = new JMenuItem("Level down :(");
	
	// File chooser
	final JFileChooser m_fileChooser = new JFileChooser("data");
	
	// GUI elements
	private JTabbedPane m_pane = new JTabbedPane();
	
	// State info
	private File m_currentFile = null;
	private Team m_team = null;
	private List<PlayerPanel> m_playerPanels = null;
	private Player m_lastSelectedPlayer = null;
	
	public Team m_team_A=null;
	public Team m_team_B=null;
	
	/**
	 * Default and only constructor. Creates the TeamEditor window.
	 */
	public TeamEditor()
	{
		super("Bloodbowl Team Editor");
		
		// Setup main frame
        getContentPane().setPreferredSize(new Dimension(300, 500));
		getContentPane().add(m_pane);
		
		// Set menu bar
		setJMenuBar(m_menu);
		
		// Build menus
		m_file.add(m_new);
		m_file.add(m_open);
		m_file.add(m_save);
		m_file.add(m_saveAs);
		m_file.add(m_quit);
		
		m_menu.add(m_file);
		
		m_actions.add(m_addPlayer);
		m_actions.add(m_removePlayer);
		m_actions.add(m_levelUpPlayer);
		m_actions.add(m_levelDownPlayer);
		
		m_menu.add(m_actions);
		
		// Allow swing app to be closed
		setDefaultCloseOperation(DISPOSE_ON_CLOSE);
		
		// Add menu callbacks
		m_new.addActionListener(this);
		m_open.addActionListener(this);
		m_save.addActionListener(this);
		m_saveAs.addActionListener(this);
		m_quit.addActionListener(this);
		
		m_addPlayer.addActionListener(this);
		m_removePlayer.addActionListener(this);
		m_levelUpPlayer.addActionListener(this);
		m_levelDownPlayer.addActionListener(this);
		
		this.pack();
		this.setVisible(true);
	}
	
	@Override
	/**
	 * Overriden from ActionListener class.
	 * Takes care of menu interaction.
	 */
	public void actionPerformed(ActionEvent event) 
	{
		//////////////////////////////////////////////////////
		// File submenu                                     //
		//////////////////////////////////////////////////////
		if( event.getSource() == m_new )
		{
			// First, ask for team name
			String teamName = (String)JOptionPane.showInputDialog(this,
					                                              "Please enter the team name:",
					                                              "New Team",
					                                              JOptionPane.PLAIN_MESSAGE,
					                                              null,
					                                              null,
					                                              "");
			if( teamName == null || teamName.length() == 0 )
				return;
			
			// Then ask for race
			Race teamRace = (Race)JOptionPane.showInputDialog(this,
															  "Please select the team race:",
															  "New Team",
															  JOptionPane.PLAIN_MESSAGE,
															  null,
															  Race.values(),
															  Race.HUMAN );
			
			if( teamRace == null  )
				return;
			
			// Create new team
			m_team = new Team(teamName, teamRace);
			m_team.resetToDefault();
			
			m_currentFile = null;
			m_lastSelectedPlayer = null;
			Update();
		}
		else if( event.getSource() == m_open )
		{
			// Open a standard file dialog, looking for xml files
			int returnVal = m_fileChooser.showOpenDialog(this);
			if( returnVal == JFileChooser.APPROVE_OPTION )
			{
				m_currentFile = m_fileChooser.getSelectedFile();
				System.out.println("Loading team: " + m_currentFile.getName());
			
				Team loadedTeam = null;
				try
				{
					loadedTeam = new Team(m_currentFile);
					m_team = loadedTeam;
					m_lastSelectedPlayer = null;
					Update();
				}
				catch( IOException e )
				{
					JOptionPane.showMessageDialog(this, "The file couldn't be opened successfully.", "File open error", JOptionPane.ERROR_MESSAGE);
				}
				catch( SAXException e )
				{
					JOptionPane.showMessageDialog(this, "The file doesn't contain a valid team, or it is corrupted.", "Team load error", JOptionPane.ERROR_MESSAGE);
				}
			}			
		}
		else if( event.getSource() == m_save )
		{
			// Early exit: no team
			if( m_team == null )
				return;
			
			// If the team was saved before (or opened), save directly
			// else, same path as "save as"
			boolean saveToFile = true;
			
			if( m_currentFile == null )
			{
				int returnVal = m_fileChooser.showSaveDialog(this);
				if( returnVal == JFileChooser.APPROVE_OPTION )
				{
					m_currentFile = m_fileChooser.getSelectedFile();
				}
				else
				{
					saveToFile = false;
				}
			}
			
			if( saveToFile )
			{
				SaveTeamToFile();
			}
		}
		else if( event.getSource() == m_saveAs )
		{
			// Early exit: no team
			if( m_team == null )
				return;
			
			// Ask to which file to save, save.
			int returnVal = m_fileChooser.showSaveDialog(this);
			if( returnVal == JFileChooser.APPROVE_OPTION )
			{
				m_currentFile = m_fileChooser.getSelectedFile();
				SaveTeamToFile();
			}
		}
		else if( event.getSource() == m_quit )
		{
			dispose();
		}
		///////////////////////////////////////////////////
		// Team submenu                                  //
		///////////////////////////////////////////////////
		else if( event.getSource() == m_addPlayer )
		{
			if( m_team != null )
			{
				Role playerRole = (Role)JOptionPane.showInputDialog(this,
						                                            "Please select new player role:",
						                                            "New player",
						                                            JOptionPane.PLAIN_MESSAGE,
						                                            null,
						                                            Role.values(),
						                                            Role.LINEMAN );
				
				if( playerRole == null )
					return;
				
				int unusedPlayerNumber = m_team.getUnusedPlayerNumber();
				Player newPlayer = new Player( "New " + playerRole.toString() + " #" + Integer.toString(unusedPlayerNumber), 
						                       m_team.getRace(), 
						                       playerRole, 
						                       unusedPlayerNumber );
				
				try
				{
					m_team.addPlayer(newPlayer);
					m_lastSelectedPlayer = newPlayer;
					Update();
				} catch( TeamException e )
				{
					JOptionPane.showMessageDialog(this, e.getMessage(), "Add new player error", JOptionPane.ERROR_MESSAGE);
				}
			}
			else
			{
				JOptionPane.showMessageDialog(this, "Please open or create a new team before trying to add players", "Add new player error", JOptionPane.ERROR_MESSAGE);
			}
		}
		else if( event.getSource() == m_removePlayer )
		{
			if( m_team == null )
			{
				JOptionPane.showMessageDialog(this, "Please open or create a new team before trying to add players", "Remove player error", JOptionPane.ERROR_MESSAGE);
			}
			else if( m_team.players().size() == 0 )
			{
				JOptionPane.showMessageDialog(this, "The team is already empty, there is no player to remove.", "Remove player error", JOptionPane.ERROR_MESSAGE);
			}
			else
			{
				PlayerPanel panel = (PlayerPanel) m_pane.getSelectedComponent();
				if( panel == null )
					return;
				
				Player player = panel.getPlayer();
				
				try
				{
					m_team.removePlayer(player);
					m_lastSelectedPlayer = null;
					Update();
				} catch( TeamException e )
				{
					JOptionPane.showMessageDialog(this, e.getMessage(), "Remove player error", JOptionPane.ERROR_MESSAGE);
				}			
			}
		}
		else if( event.getSource() == m_levelUpPlayer )
		{
			if( m_team == null )
			{
				JOptionPane.showMessageDialog(this, "Please open or create a new team before trying to level up players", "Level up error", JOptionPane.ERROR_MESSAGE);
			}
			else if( m_team.players().size() == 0 )
			{
				JOptionPane.showMessageDialog(this, "In order to level up a player, there must be a player", "Level up error", JOptionPane.ERROR_MESSAGE);
			}
			else
			{
				PlayerPanel panel = (PlayerPanel) m_pane.getSelectedComponent();
				
				// Panel must be commited to prevent issues with the xp not up to date, etc.
				panel.commit();
				
				Player player = panel.getPlayer();
				
				if( player.canLevelUp() )
				{
					// Increment player level
					player.setLevel(player.level()+1);
					
					// Perform improvement rolls
					int dice1 = Dice.rollDice(6);
					int dice2 = Dice.rollDice(6);
					int result = dice1 + dice2;
					boolean isDouble = (dice1 == dice2);
					
					// 2-9 skill gain
					// 10 MA, AV or skill
					// 11 AG or skill
					// 12 ST or skill
					boolean addSkill = true;
					
					if( result >= 10 )
					{
						String skillChoice = "New Skill";
						if( isDouble )
							skillChoice += " (incl. doubles!)";

						String[] choices = null;
						
						if( result == 10 )
							choices = new String[]{ skillChoice, "Improve MA", "Improve AV" };
						else if( result == 11 )
							choices = new String[]{ skillChoice, "Improve AG" };
						else // result == 12
							choices = new String[]{ skillChoice, "Improve ST" };						
						
						String choice = (String)JOptionPane.showInputDialog(this, "Roll of " + result +" ! Improve stat or select skill?", "Leveling up", JOptionPane.PLAIN_MESSAGE, null, (Object[])choices, (Object)choices[0] );
						
						if( !choice.equalsIgnoreCase(skillChoice) )
							addSkill = false;
					
						if( choice.equalsIgnoreCase("Improve MA") )
							player.setMA( player.MA() + 1 );
						else if( choice.equalsIgnoreCase("Improve AV") )
							player.setAV( player.AV() + 1 );
						else if( choice.equalsIgnoreCase("Improve AG") )
							player.setAG( player.AG() + 1 );
						else if( choice.equalsIgnoreCase("Improve ST") )
							player.setST( player.ST() + 1 );						
					}
					
					if( addSkill )
					{
						// Pop new skill dialog
						// TODO IF NEEDED : Select skills only from the good category, possibly including the "double" categories when it happens
						ArrayList<Skill> skills = new ArrayList<Skill>();
						for( Skill skill : Skill.values() )
						{
							if( !player.hasSkill(skill) )
								skills.add(skill);
						}
						
						Skill newSkill = (Skill)JOptionPane.showInputDialog(this, "Select the skill to learn", "Leveling up", JOptionPane.PLAIN_MESSAGE, null, skills.toArray(), (Object)skills.get(0) );
						player.addSkill(newSkill);
					}
					
					m_lastSelectedPlayer = player;
					Update();
				}
				else
				{
					JOptionPane.showMessageDialog(this, "Player is not eligible for level up", "Level up error", JOptionPane.ERROR_MESSAGE);
				}
			}
		}
		else if( event.getSource() == m_levelDownPlayer )
		{
			if( m_team == null )
			{
				JOptionPane.showMessageDialog(this, "Please open or create a new team before trying to level down players", "Level down error", JOptionPane.ERROR_MESSAGE);
			}
			else if( m_team.players().size() == 0 )
			{
				JOptionPane.showMessageDialog(this, "In order to level down a player, there must be a player", "Level down error", JOptionPane.ERROR_MESSAGE);
			}
			else
			{
				PlayerPanel panel = (PlayerPanel) m_pane.getSelectedComponent();
				
				// Panel must be commited to prevent issues with the xp not up to date, etc.
				panel.commit();
				
				Player player = panel.getPlayer();

				if( player.canLevelDown() )
				{
					// Decrement player level
					player.setLevel(player.level() - 1);
					
					// Remove either a stat or a skill, ask user!
					Player defaultPlayer = new Player("dummy", player.race(), player.role(), 0);
					
					String removeMA = "Decrease MA";
					String removeST = "Decrease ST";
					String removeAG = "Decrease AG";
					String removeAV = "Decrease AV";
					String removeSkill = "Lose Skill";

					ArrayList<String> options = new ArrayList<String>();
				
					// Add the "remove stat" option ONLY if the player increased it before
					if( player.MA() > defaultPlayer.MA() )
						options.add(removeMA);
					
					if( player.ST() > defaultPlayer.ST() )
						options.add(removeST);
					
					if( player.AG() > defaultPlayer.AG() )
						options.add(removeAG);
					
					if( player.AV() > defaultPlayer.AV() )
						options.add(removeAV);
					
					if( player.skills().size() > defaultPlayer.skills().size() )
						options.add(removeSkill);
					
					if( options.size() > 0 )
					{
						String choice = (String)JOptionPane.showInputDialog(this, "Select what leveling down options suits better.", "Leveling down", JOptionPane.PLAIN_MESSAGE, null, options.toArray(), options.get(0) );
						
						if( choice.equals(removeMA) )
							player.setMA( player.MA() - 1 );
						else if( choice.equals(removeST) )
							player.setST( player.ST() - 1 );
						else if( choice.equals(removeAG) )
							player.setAG( player.AG() - 1 );
						else if( choice.equals(removeAV) )
							player.setAV( player.AV() - 1 );
						else if( choice.equals(removeSkill) )
						{
							// Coach selected to remove a skill.
							// Create a list containing the skills that can be removed -- i.e. player skills - default skills
							ArrayList<Skill> skills = new ArrayList<Skill>();
							for( Skill skill : player.skills() )
								if( !defaultPlayer.skills().contains(skill) )
									skills.add(skill);
							
							Skill skillToLose = (Skill)JOptionPane.showInputDialog(this, "Select what skill to lose", "Losing a skill", JOptionPane.PLAIN_MESSAGE, null, skills.toArray(), skills.get(0) );
							
							player.removeSkill(skillToLose);
						}
						
						m_lastSelectedPlayer = player;
						Update();
					}
					else
					{
						JOptionPane.showMessageDialog(this, "Player has an increased level, but no perks.. Looks like a manual edit that went wrong!", "Level down error", JOptionPane.ERROR_MESSAGE );
					}
				}
				else
				{
					JOptionPane.showMessageDialog(this, "Player is not eligible for level down (too much XP or level 1!)", "Level down error", JOptionPane.ERROR_MESSAGE );
				}
			}
		}
	}

	
	/**
	 * This method is called when the team has changed (either from a new or a load operation)
	 */
	private void Update()
	{
		// Update window title after an operation
		if( m_team == null )
			setTitle("Bloodbowl Team Editor");
		else
			setTitle("Bloodbowl Team Editor - " + m_team.getName() + " " + m_team.getRace().toString() );
		
		// If there were any previously created panels, remove them
		m_pane.removeAll();
		m_playerPanels = null;
		
		if( m_team != null )
		{
			m_playerPanels = new ArrayList<PlayerPanel>();
			PlayerPanel selectedPanel = null;
			
			for( Player player : m_team.players() )
			{
				PlayerPanel panel = new PlayerPanel(player, m_team);
				m_playerPanels.add( panel );
				m_pane.addTab(player.name(), null, panel, null);
				
				if(player == m_lastSelectedPlayer)
					selectedPanel = panel;
			}
			
			if( selectedPanel != null )
				m_pane.setSelectedComponent(selectedPanel);
		}
		
		pack();
	}

	/**
	 * Performs the necessary steps to save the team to a file (selected in m_currentFile member)
	 */
	private void SaveTeamToFile()
	{
		CommitPlayerData();
		m_team.save(m_currentFile);
	}
	
	/**
	 * This method calls the Commit method on all PlayerPanels, effectively updating the Player objects
	 */
	private void CommitPlayerData()
	{
		for( PlayerPanel panel : m_playerPanels )
		{
			panel.commit();
		}
	}
				
	/**
	 * @param args Not used.
	 */
	public static void main(String[] args) {
		new TeamEditor();
	}
}
